package com.kly.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.Db;
import com.kly.domain.dto.user.UserDto;
import com.kly.domain.dto.user.UserFollowRequest;
import com.kly.domain.dto.user.UserLoginBySmsRequest;
import com.kly.domain.dto.user.UserQueryRequest;
import com.kly.domain.entity.Picture;
import com.kly.domain.entity.User;
import com.kly.domain.entity.UserFollow;
import com.kly.domain.vo.LoginUserVo;
import com.kly.domain.vo.PictureVO;
import com.kly.domain.vo.ScrollResult;
import com.kly.domain.vo.UserVO;
import com.kly.exception.BusinessException;
import com.kly.exception.ErrorCode;
import com.kly.exception.ThrowUtils;
import com.kly.manager.auth.StpKit;
import com.kly.mapper.UserMapper;
import com.kly.service.UserFollowService;
import com.kly.service.UserService;
import com.kly.utils.DeviceUtils;
import com.kly.utils.SendEmailUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.kly.constants.RedisConstant.*;
import static com.kly.constants.UserConstant.USER_LOGIN_STATE;

/**
 * @author admin
 * @description 针对表【user(用户)】的数据库操作Service实现
 * @createDate 2025-03-02 18:28:08
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    private static final String PHONE_REGEX = "^1[3-9]\\\\d{9}";
    @Resource
    private UserFollowService userFollowService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private SendEmailUtil sendEmailUtil;

    @Override
    public Long userRegister(String userAccount, String userPassword, String checkPassword) {
        // 1. 校验参数
        // if(StrUtil.hasBlank(userAccount, userPassword, checkPassword)) {
        //     throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        // }
        ThrowUtils.throwIf(StrUtil.hasBlank(userAccount, userPassword, checkPassword),
                ErrorCode.PARAMS_ERROR);
        // 判断用户名是否有特殊字符 使用正则表达式
        String validPattern = "^[\\u4e00-\\u9fa5a-zA-Z0-9_]+$";
        ThrowUtils.throwIf(!userAccount.matches(validPattern),
                ErrorCode.PARAMS_ERROR, "用户名包含特殊字符");
        // 密码验证 不能含有空格
        ThrowUtils.throwIf(userPassword.contains(" "),
                ErrorCode.PARAMS_ERROR, "密码不能包含空格");


        ThrowUtils.throwIf(userAccount.length() < 4, ErrorCode.PARAMS_ERROR, "用户账号过短");
        ThrowUtils.throwIf(userPassword.length() < 8 || checkPassword.length() < 8,
                ErrorCode.PARAMS_ERROR, "用户密码过短");
        ThrowUtils.throwIf(userAccount.length() > 16, ErrorCode.PARAMS_ERROR, "用户账号过长");
        ThrowUtils.throwIf(userPassword.length() > 16, ErrorCode.PARAMS_ERROR, "用户密码过长");
        ThrowUtils.throwIf(!userPassword.equals(checkPassword), ErrorCode.PARAMS_ERROR, "两次密码不一致");

        // 2. 校验账户是否重复
        Long count = this.lambdaQuery()
                .eq(User::getUserAccount, userAccount)
                .count();
        ThrowUtils.throwIf(count > 0, ErrorCode.PARAMS_ERROR, "用户账号重复");
        // 3. 密码加密 盐
        String encryptedPassword = this.getEncryptedPassword(userPassword);
        // 4. 插入数据
        User user = new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptedPassword);
        // 生成随机用户名
        String username = UUID.randomUUID().toString().substring(0, 6);
        user.setUserName("用户" + username);

        boolean save = this.save(user);
        ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "注册失败");

        return user.getId();
    }


    @Override
    public String getEncryptedPassword(String userPassword) {
        ThrowUtils.throwIf(StrUtil.isBlank(userPassword), ErrorCode.PARAMS_ERROR);
        final String salt = "kly";
        return DigestUtils.md5DigestAsHex((salt + userPassword).getBytes());
    }

    @Override
    public String userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 1. 校验参数
        ThrowUtils.throwIf(StrUtil.hasBlank(userAccount, userPassword),
                ErrorCode.PARAMS_ERROR, "参数为空");
        ThrowUtils.throwIf(userAccount.length() < 4, ErrorCode.PARAMS_ERROR, "用户账号过短");
        ThrowUtils.throwIf(userPassword.length() < 8, ErrorCode.PARAMS_ERROR, "用户密码过短");
        ThrowUtils.throwIf(userAccount.length() > 16, ErrorCode.PARAMS_ERROR, "用户账号过长");
        ThrowUtils.throwIf(userPassword.length() > 16, ErrorCode.PARAMS_ERROR, "用户密码过长");
        // 2. 校验账户是否存在
        User userDb = this.lambdaQuery()
                .eq(User::getUserAccount, userAccount)
                .one();
        ThrowUtils.throwIf(userDb == null, ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
        // 3. 校验密码是否正确
        ThrowUtils.throwIf(!this.getEncryptedPassword(userPassword).equals(userDb.getUserPassword()),
                ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
        // 4. 返回脱敏后的用户信息
        LoginUserVo loginUserVo = this.getLoginUserVo(userDb);
        // 5. 登录成功，记录登录态
        // request.getSession().setAttribute(USER_LOGIN_STATE, BeanUtil.copyProperties(loginUserVo, UserDto.class));
        // String token = UUID.randomUUID().toString(true);
        // String key = USER_LOGIN_INFO_KEY + token;
        // Map<String, String> userMap = new HashMap<>();
        // userMap.put("id", userDb.getId().toString());
        // userMap.put("userAccount", userDb.getUserAccount());
        // stringRedisTemplate.opsForHash().putAll(key, userMap);
        // stringRedisTemplate.expire(key, USER_LOGIN_INFO_TTL, TimeUnit.SECONDS);
        // 记录sa-token登陆态
        UserDto userDto = BeanUtil.copyProperties(loginUserVo, UserDto.class);
        // StpUtil.login(userDto.getId());
        // StpUtil.getSession().set(USER_LOGIN_STATE, userDto);
        StpKit.SPACE.login(userDto.getId(), DeviceUtils.getRequestDevice(request));
        StpKit.SPACE.getSession().set(USER_LOGIN_STATE, userDto);

        // 删除验证码 防止重复使用
        String limitKey = LOGIN_CODE_LIMIT_KEY + userAccount;
        String loginKey = LOGIN_CODE_KEY + userAccount;
        stringRedisTemplate.delete(limitKey);
        stringRedisTemplate.delete(loginKey);
        return null;
    }

    @Override
    public LoginUserVo getLoginUserVo(User userDb) {
        if (userDb == null) {
            return null;
        }
        return BeanUtil.copyProperties(userDb, LoginUserVo.class);
    }

    @Override
    public UserVO getUserVo(User user) {
        if (user == null) {
            return null;
        }
        return BeanUtil.copyProperties(user, UserVO.class);
    }

    @Override
    public List<UserVO> getUserVoList(List<User> userList) {
        if (CollUtil.isEmpty(userList)) {
            return Collections.emptyList();
        }

        return userList.stream()
                .map(this::getUserVo)
                .collect(Collectors.toList());
    }


    @Override
    public QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest) {
        if (userQueryRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
        }
        Long id = userQueryRequest.getId();
        String userAccount = userQueryRequest.getUserAccount();
        String userName = userQueryRequest.getUserName();
        String userProfile = userQueryRequest.getUserProfile();
        String userRole = userQueryRequest.getUserRole();
        String sortField = userQueryRequest.getSortField();
        String sortOrder = userQueryRequest.getSortOrder();
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(Objects.nonNull(id), "id", id);
        queryWrapper.eq(StrUtil.isNotBlank(userRole), "user_role", userRole);
        queryWrapper.like(StrUtil.isNotBlank(userAccount), "user_account", userAccount);
        queryWrapper.like(StrUtil.isNotBlank(userName), "user_name", userName);
        queryWrapper.like(StrUtil.isNotBlank(userProfile), "user_profile", userProfile);
        queryWrapper.orderBy(StrUtil.isNotBlank(sortField), sortOrder.equals("ascend"), sortField);
        return queryWrapper;
    }


    @Override
    public User getLoginUser(HttpServletRequest request) {
        // Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        long userId = StpKit.SPACE.getLoginIdAsLong();
        return this.getById(userId);
    }

    @Override
    public boolean userLogout(HttpServletRequest request) {
        StpKit.SPACE.logout();
        return true;
    }

    @Override
    public boolean followUser(UserFollowRequest userFollowRequest, User loginUser) {
        // 1， 校验参数
        ThrowUtils.throwIf(userFollowRequest == null, ErrorCode.PARAMS_ERROR);
        ThrowUtils.throwIf(loginUser == null, ErrorCode.NOT_LOGIN_ERROR);
        Long followedUserId = userFollowRequest.getFollowedUserId();
        Boolean isFollow = userFollowRequest.getIsFollow();
        Long userId = loginUser.getId();
        ThrowUtils.throwIf(followedUserId == null || isFollow == null, ErrorCode.PARAMS_ERROR);
        ThrowUtils.throwIf(userId.equals(followedUserId), ErrorCode.PARAMS_ERROR, "不能关注自己");
        // 2. 判断是否已关注
        String key = USER_FOLLOW_KEY + userId;
        if (!isFollow) {
            // 3. 关注
            UserFollow userFollow = new UserFollow();
            userFollow.setFollowingUserId(userId);
            userFollow.setFollowedUserId(followedUserId);
            boolean save = userFollowService.save(userFollow);
            ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "关注失败");
            // 存储到redis
            stringRedisTemplate.opsForSet().add(key, followedUserId.toString());
            return true;
        } else {
            // 4. 取关
            boolean remove = userFollowService.lambdaUpdate()
                    .eq(UserFollow::getFollowingUserId, userId)
                    .eq(UserFollow::getFollowedUserId, followedUserId)
                    .remove();
            ThrowUtils.throwIf(!remove, ErrorCode.OPERATION_ERROR, "取关失败");
            // 移除Redis缓存
            stringRedisTemplate.opsForSet().remove(key, followedUserId.toString());
            return true;
        }
    }

    @Override
    public List<User> commonFollowUser(Long followedUserId, Long userId) {
        // 校验参数
        ThrowUtils.throwIf(followedUserId == null || userId == null, ErrorCode.PARAMS_ERROR);
        if (followedUserId.equals(userId)) {
            return Collections.emptyList();
        }
        // 查询缓存
        String key1 = USER_FOLLOW_KEY + userId;
        String key2 = USER_FOLLOW_KEY + followedUserId;
        // 求交集
        Set<String> intersectIds = stringRedisTemplate.opsForSet().intersect(key1, key2);
        if (CollUtil.isEmpty(intersectIds)) {
            return Collections.emptyList();
        }
        // 转换id
        List<Long> idList = intersectIds.stream().map(Long::valueOf).collect(Collectors.toList());
        // 返回
        if (CollUtil.isEmpty(idList)) {
            return Collections.emptyList();
        }
        return this.lambdaQuery()
                .in(User::getId, idList)
                .list();
    }

    @Override
    public ScrollResult<PictureVO> queryFollowList(Long max, Integer offset, Long userId) {
        // 校验参数
        ThrowUtils.throwIf(max == null || offset == null, ErrorCode.PARAMS_ERROR);
        ThrowUtils.throwIf(userId == null, ErrorCode.NOT_LOGIN_ERROR);
        // 查询缓存
        String key = PICTURE_FEED_KEY + userId;
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, max, offset, 2);
        // 判断非空
        if (CollUtil.isEmpty(typedTuples)) {
            return new ScrollResult<>();
        }
        // 解析数据id，score
        List<Long> idList = new ArrayList<>(typedTuples.size());
        long minTime = 0L;
        int count = 1;
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            //     获取id
            idList.add(Long.valueOf(Objects.requireNonNull(typedTuple.getValue())));
            //     获取最小时间
            long time = Objects.requireNonNull(typedTuple.getScore()).longValue();
            if (time == minTime) {
                count++;
            } else {
                minTime = time;
                count = 1;
            }
        }
        count = minTime == max ? count : count + offset;
        // 查询Picture
        String idStr = StrUtil.join(",", idList);
        List<Picture> pictureList = Db.lambdaQuery(Picture.class)
                .in(Picture::getId, idList)
                .last(String.format("order by field(id, %s)", idStr))
                .list();
        List<PictureVO> pictureVOList = BeanUtil.copyToList(pictureList, PictureVO.class);

        for (PictureVO pictureVO : pictureVOList) {
            // 获取用户信息
            User user = this.getById(pictureVO.getUserId());
            UserVO userVO = this.getUserVo(user);
            pictureVO.setUser(userVO);
        }

        // 返回数据
        ScrollResult<PictureVO> scrollResult = new ScrollResult<>();
        scrollResult.setRecords(pictureVOList);
        scrollResult.setOffset(count);
        scrollResult.setMinTime(minTime);
        return scrollResult;
    }

    @Override
    public String userLoginBySms(UserLoginBySmsRequest userLoginBySmsRequest) {
        // 校验参数
        ThrowUtils.throwIf(userLoginBySmsRequest == null, ErrorCode.PARAMS_ERROR);
        String phone = userLoginBySmsRequest.getPhone();
        String code = userLoginBySmsRequest.getCode();
        ThrowUtils.throwIf(StrUtil.hasBlank(phone, code), ErrorCode.PARAMS_ERROR);
        //     1. 从redis中获取验证码
        String codeKey = USER_LOGIN_CODE_KEY + phone;
        String codeCache = stringRedisTemplate.opsForValue().get(codeKey);
        //     2. 判断验证码是否正确
        ThrowUtils.throwIf(!StrUtil.equals(code, codeCache), ErrorCode.PARAMS_ERROR, "验证码错误");
        //     3. 查询用户是否存在 悲观锁
        synchronized (phone.intern()) {
            User userDb = this.lambdaQuery()
                    .eq(User::getPhone, phone)
                    .one();
            if (userDb == null) {
                //     创建用户
                userDb = new User();
                userDb.setPhone(phone);
                // 生成账号 随机
                userDb.setUserAccount(RandomUtil.randomString(10));
                String encryptedPassword = getEncryptedPassword("12345678");
                userDb.setUserPassword(encryptedPassword);
                //     保存数据库
                boolean save = this.save(userDb);
                ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "创建用户失败");
            }
            //     4. 生成token 保存用户信息到redis中 hash
            String token = UUID.randomUUID().toString(true);
            Map<String, String> userMap = new HashMap<>();
            userMap.put("id", userDb.getId().toString());
            userMap.put("phone", userDb.getPhone());
            String key = USER_LOGIN_INFO_KEY + token;
            stringRedisTemplate.opsForHash().putAll(key, userMap);
            // 设置过期时间
            stringRedisTemplate.expire(key, USER_LOGIN_INFO_TTL, TimeUnit.SECONDS);

            //     返回token
            return token;
        }

    }

    @Override
    public void sendCode(String phone) {
        //     1. 判断参数是否为空
        ThrowUtils.throwIf(StrUtil.isBlank(phone), ErrorCode.PARAMS_ERROR);
        //     3. 生成验证码 6位随机数
        String code = RandomUtil.randomNumbers(6);
        //     4. 发送验证码
        log.info("发送验证码成功，验证码为：{}", code);
        //     5. 保存验证码到redis中
        String codeKey = USER_LOGIN_CODE_KEY + phone;
        stringRedisTemplate.opsForValue().set(codeKey, code, USER_LOGIN_CODE_TTL, TimeUnit.MINUTES);
    }

    @Override
    public void sendCodeByEmail(String email) {
        ThrowUtils.throwIf(StrUtil.isBlank(email), ErrorCode.PARAMS_ERROR);
        //     生成验证码
        checkEmail(email);
        String code = RandomUtil.randomNumbers(6);
        //     发送验证码
        sendEmailUtil.sendCodeEmail(email, code);
        //     存储验证码
        String codeKey = USER_LOGIN_CODE_EMAIL_KEY + email;
        stringRedisTemplate.opsForValue().set(codeKey, code, USER_LOGIN_CODE_EMAIL_TTL, TimeUnit.MINUTES);
    }


    @Override
    public LoginUserVo userLoginByEmail(UserLoginBySmsRequest userLoginBySmsRequest, HttpServletRequest request) {
        // 校验参数
        ThrowUtils.throwIf(userLoginBySmsRequest == null, ErrorCode.PARAMS_ERROR);
        String email = userLoginBySmsRequest.getEmail();
        String code = userLoginBySmsRequest.getCode();
        ThrowUtils.throwIf(StrUtil.hasBlank(email, code), ErrorCode.PARAMS_ERROR);
        checkEmail(email);
        // 获取缓存验证码
        String codeKey = USER_LOGIN_CODE_EMAIL_KEY + email;
        String codeCache = stringRedisTemplate.opsForValue().get(codeKey);
        // 判断验证码是否正确
        ThrowUtils.throwIf(!StrUtil.equals(code, codeCache), ErrorCode.PARAMS_ERROR, "验证码错误");
        // 未注册进行注册
        User userDb = this.lambdaQuery().eq(User::getEmail, email).one();
        if (userDb == null) {
            userDb = new User();
            userDb.setEmail(email);
            userDb.setUserAccount(RandomUtil.randomString(10));
            userDb.setUserPassword(getEncryptedPassword("12345678"));
            boolean save = this.save(userDb);
            ThrowUtils.throwIf(!save, ErrorCode.OPERATION_ERROR, "创建用户失败");
        }
        // 开始登录
        LoginUserVo loginUserVo = getLoginUserVo(userDb);
        // 5. 登录成功，记录登录态
        // request.getSession().setAttribute(USER_LOGIN_STATE, BeanUtil.copyProperties(loginUserVo, UserDto.class));
        UserDto userDto = BeanUtil.copyProperties(loginUserVo, UserDto.class);
        // StpUtil.login(userDto.getId());
        // StpUtil.getSession().set(USER_LOGIN_STATE, userDto);
        StpKit.SPACE.login(userDto.getId(), DeviceUtils.getRequestDevice(request));
        StpKit.SPACE.getSession().set(USER_LOGIN_STATE, userDto);
        // 存储用户信息
        // Map<String, String> userMap = new HashMap<>();
        // userMap.put("id", userDb.getId().toString());
        // userMap.put("email", userDb.getEmail());
        // stringRedisTemplate.opsForHash().putAll(USER_LOGIN_INFO_KEY + token, userMap);
        // loginUserVo.setToken(token);
        // 删除验证码 防止重复使用
        String limitKey = LOGIN_CODE_LIMIT_KEY + email;
        String loginKey = LOGIN_CODE_KEY + email;
        stringRedisTemplate.delete(limitKey);
        stringRedisTemplate.delete(loginKey);
        return loginUserVo;
    }

    @Override
    public void checkEmail(String email) {
        // 定义正则表达式
        String EMAIL_REGEX = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$";
        ThrowUtils.throwIf(!email.matches(EMAIL_REGEX), ErrorCode.PARAMS_ERROR, "邮箱格式错误");
    }
}




